package com.huiyeji.crypt;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Random;

public class R2Coder implements Coder{

    private final int rc;

    public static final R2Coder instance_3 = new R2Coder(3);
    public static final R2Coder instance_2 = new R2Coder(2);
    public static final R2Coder instance_1 = new R2Coder(1);

    public R2Coder(int rc){
        if (rc < 1 || rc > 10){
            throw new IllegalArgumentException("rc must be between 1 and 10");
        }
        this.rc = rc;
    }

    public R2Coder(){
        this(1);
    }

    public String encode(String text){
        return new String(encode(text.getBytes(StandardCharsets.UTF_8)),StandardCharsets.ISO_8859_1);
    }

    public String decode(String text){
        return new String(decode(text.getBytes(StandardCharsets.ISO_8859_1)),StandardCharsets.UTF_8);
    }


    @Override
    public byte[] encode(byte[] bytes) {
        return encodeRC(bytes,0);
    }

    private byte[] encodeRC(byte[] bytes,int rc){
        if (bytes == null || bytes.length == 0){
            return new byte[0];
        }

        if (rc == this.rc){
            return bytes;
        }

        byte r = (byte) (new Random().nextInt(126)+1);
        byte[] encodedBytes = new byte[bytes.length * 3 + 1];
        encodedBytes[0] = r;
        for (int i = 0; i < bytes.length; i++) {
            int sub = Math.abs(Math.abs(bytes[i]) - r);
            encodedBytes[1 + i * 3] = (byte) sub;
            encodedBytes[2 + i * 3] = bytes[i] > 0? (byte) -randomByte() : randomByte();
            encodedBytes[3 + i * 3] = Math.abs(bytes[i]) > r ? randomByte() : (byte) -randomByte();
        }

        return encodeRC(encodedBytes,++rc);
    }

    @Override
    public byte[] decode(byte[] bytes) {
        return decodeRC(bytes,0);
    }

    private byte[] decodeRC(byte[] bytes,int rc){
        if (bytes == null || bytes.length == 0){
            return new byte[0];
        }

        if (rc == this.rc){
            return bytes;
        }

        byte[] decodedBytes = new byte[(bytes.length - 1)  / 3];
        byte r = bytes[0];
        for (int i = 0; i < decodedBytes.length; i++) {
            byte sub = bytes[1 + i * 3];
            byte sign = bytes[2 + i * 3];
            byte up = bytes[3 + i * 3];
            byte tem = (byte) (up > 0 ? sub + r : r - sub);
            decodedBytes[i] = sign > 0 ? (byte) - tem :  tem;
        }

        return decodeRC(decodedBytes,++rc);
    }

    private byte randomByte(){
        return (byte) (new Random().nextInt(126) + 1);
    }

    public static void main(String[] args) throws IOException {
//        byte[] encode = instance_1.encode(("").getBytes(StandardCharsets.UTF_8));
//
//        FileOutputStream writer = new FileOutputStream("C:/Users/HW/Desktop/Test2Plugin.hsp.dy.plugin");
//        writer.write(encode);
//        writer.flush();
//        writer.close();

//        R2Coder.instance_1.encodeFile("C:/Users/HW/Desktop/Test2Plugin.hsp.dy.plugin");
        R2Coder.instance_1.encodeFile("C:/Users/HW/Desktop/Test2Plugin.hsp.dy.plugin");

        //R3Coder.instance.decodeFile("C:/Users/HW/Desktop/Test2Plugin.hsp.dy.plugin");


    }

    public void encodeFile(String filePath) throws IOException {
        codeFile(filePath,true);
    }

    public void decodeFile(String filePath) throws IOException {
        codeFile(filePath,false);
    }

    private void codeFile(String filePath,boolean isEncode) throws IOException {
        Path path = Paths.get(filePath);
        BufferedInputStream bio = null;
        ByteArrayOutputStream buffer = null;
        BufferedOutputStream bo = null;
        try {
            bio = new BufferedInputStream(Files.newInputStream(path));
            buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[16384];
            while ((nRead = bio.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            byte[] encodeArray = isEncode ? encode(buffer.toByteArray()) : decode(buffer.toByteArray());
            bo = new BufferedOutputStream(Files.newOutputStream(path));
            bo.write(encodeArray);
            bo.flush();
        } catch (Exception exception){
            exception.printStackTrace();
        } finally {
            if (bio != null){
                bio.close();
            }

            if (buffer != null){
                buffer.close();
            }

            if (bo != null){
                bo.close();
            }
        }
    }
}
